#include "board.h"
#include "queue.h"
#include "stack.h"
#include <stdlib.h>
#include <memory.h>

const int FALSE = 0;
const int TRUE = 1;

/* Implementacja planszy */
typedef struct Data {

	/* "boolowskie" wartości, czy gracze są komputerowi? */
	int isCircleComputer;
	int isCrossComputer;

	/* punkty z licytacji */
	float komiCircle;
	float komiCross;

	/* bieżący wynik */
	Result result;

	/* kara za grupę */
	int groupPenalty;
	
	/* rozmiar planszy */
	int n;

	/* czyj ruch? */
	symbol currentMove;

	/* plansza = tablica symboli */
	symbol* fields;

	/* stos ruchów do undo */
	Stack* undoStack;
	
} Data;

/* Strukturka na potrzeby wyszukiwania grup */
typedef struct Vertex {

	int x;
	int y;
	
} Vertex;

/* ustawienie pola na planszy */
void board_set(Board* b, int x, int y, symbol s) {

	b->data->fields[board_size(b)*x+y] = s;	
	
}

/* Zliczamy grupy danego symbolu */
/* Problem sprowadza się do spójnych składowych w grafie */
static int group_count(Board* b, symbol s) {

	int i, j;
	symbol tempSymbol;
	
	/* dane o "kolorach pól" */ 
	int* data;

	/* bieżący kolor do kolorowania, kolor 0 oznacza
	 nieprzerobiony wierzchołek. */
	int currentColor;

	/* Kolejka na potrzeby algorytmu BzFS */
	Queue* queue;

	/* Wierzchołek */
	Vertex *v, *temp;

	/* rozmiar planszy i alokacja pamięci */
	int boardSize = board_size(b);
	data = (int*)malloc(boardSize*boardSize*sizeof(int));

	memset(data, 0, boardSize*boardSize*sizeof(int));
	currentColor = 1;

	queue_init(&queue);

	/* szukamy spójnych składowych dla każdego pola */
	for(i=0;i<boardSize;i++) {

		for(j=0;j<boardSize;j++) {

			tempSymbol = board_get(b, i, j);

			/* pola puste lub zajęte przez innego gracza pomijamy */
			/* pomijamy także pola już "przerobione" */
			if(data[i*boardSize+j] != 0 || tempSymbol != s)
				continue;

			temp = (Vertex*)malloc(sizeof(Vertex));

			temp->x = i;
			temp->y = j;

			queue_push(queue, temp);

			while(queue_size(queue) != 0) {

				v = (Vertex*)queue_pop(queue);

				/* kolorujemy pole */
				data[v->x*boardSize+v->y] = currentColor;

				/* dodajemy sąsiadów do kolejki */

				/* dolny sąsiad */
				if(v->y+1 < boardSize) {
					if(data[v->x*boardSize+(v->y+1)] == 0 && board_get(b, v->x, v->y+1) == s) {
						temp = (Vertex*)malloc(sizeof(Vertex));
						temp->x = v->x;
						temp->y = v->y+1;
						queue_push(queue, temp);
					}
				}

				/* górny sąsiad */
				if(v->y-1 >= 0) {
					if(data[v->x*boardSize+(v->y-1)] == 0 && board_get(b, v->x, v->y-1) == s) {
						temp = (Vertex*)malloc(sizeof(Vertex));
						temp->x = v->x;
						temp->y = v->y-1;
						queue_push(queue, temp);
					}
				}

				/* prawy sąsiad */
				if(v->x+1 < boardSize) {
					if(data[(v->x+1)*boardSize+(v->y)] == 0 && board_get(b, v->x+1, v->y) == s) {
						temp = (Vertex*)malloc(sizeof(Vertex));
						temp->x = v->x+1;
						temp->y = v->y;
						queue_push(queue, temp);
					}
				}

				/* lewy sąsiad */
				if(v->x-1 >= 0) {
					if(data[(v->x-1)*boardSize+(v->y)] == 0 && board_get(b, v->x-1, v->y) == s) {
						temp = (Vertex*)malloc(sizeof(Vertex));
						temp->x = v->x-1;
						temp->y = v->y;
						queue_push(queue, temp);
					}
				}

				/* Zwalniamy pamięć */
				free(v);
				
			}

			currentColor++;
			
		}
		
	}

	queue_cleanup(queue);
	
	free(data);

	return currentColor-1;

}

/* obliczamy i zwracamy wynik, funkcja prywatna... */
static Result board_calculate_result(Board* b) {

	Result ret;
	int i, j;
	int boardSize;
	symbol s;

	ret.circleResult = b->data->komiCircle;
	ret.crossResult = b->data->komiCross;

	ret.circleSymbols = 0;
	ret.crossSymbols = 0;

	ret.circleGroups = group_count (b, CIRCLE);
	ret.crossGroups = group_count(b, CROSS);

	/* odejmujemy karę za grupę */
	ret.circleResult -= b->data->groupPenalty*ret.circleGroups;
	ret.crossResult -= b->data->groupPenalty*ret.crossGroups;

	boardSize = board_size(b);

	for(i=0;i<boardSize;i++) {

		for(j=0;j<boardSize;j++) {

			s = board_get (b, i, j);

			if(s == CIRCLE) {
				ret.circleResult += 1.0f;
				ret.circleSymbols++;
			}
			else if(s == CROSS) {
				ret.crossResult += 1.0f;
				ret.crossSymbols++;
			}
			
		}
		
	}
	
	return ret;
	
}

/* sprawdza, czy ruch jest pasem */
static int is_move_pass(Move* m) {

	return (m->x < 0 || m->y < 0);
	
}

void board_init(Board** board, int n, int k, float komiCircle, float komiCross,
                int isCircleComputer, int isCrossComputer) {

	int i;
	Board* b;

	*board = (Board*)malloc(sizeof(Board));

	b = *board;
	
	b->data = (Data*)malloc(sizeof(Data));

	b->data->n = n;
	b->data->groupPenalty = k;
	b->data->currentMove = CIRCLE;
	b->data->isCircleComputer = isCircleComputer;
	b->data->isCrossComputer = isCrossComputer;
	stack_init(&(b->data->undoStack));
		
	b->data->komiCircle = komiCircle;
	b->data->komiCross = komiCross;

	b->data->fields = (symbol*)malloc(sizeof(symbol)*n*n);

	for(i=0;i<n*n;i++)
		b->data->fields[i] = EMPTY;

	b->data->result = board_calculate_result(b);
	
}

void board_cleanup(Board* b) {

	void* data;

	/* usuwamy ruchy ze stosu, ponieważ */
	/* stos nie jest właścicielem swoich obiektów */
	/* i ich nie usuwa przy wywołaniu cleanup */
	while(stack_size(b->data->undoStack) != 0) {

		data = stack_pop(b->data->undoStack);
		free(data);
		
	}

	stack_cleanup (b->data->undoStack);

	free(b->data->fields);

	free(b->data);

	free(b);
	
}

/* kopiowanie planszy */
Board* board_copy(Board* b) {

	Board* nowa;
	Move** moves;
	int i, j, stackSize, boardSize;

	/* Zwykła inicjalizacja */

	board_init(&nowa, board_size(b), board_get_penalty(b), 0.0f,
	           board_get_cross_bidding_points(b), b->data->isCircleComputer, b->data->isCrossComputer);

	boardSize = board_size(b);

	/* Rekonstrukcja planszy i ruchów */

	stackSize = stack_size(b->data->undoStack);

	if(stackSize != 0)
		moves = (Move**)stack_to_array(b->data->undoStack);

	/* wykonaj ruchy w oryginalnej kolejności */
	for(i=0;i<stackSize;i++)
		board_play_move(nowa, *(moves[i]));

	/* skopiuj zawartość planszy */
	for(i=0;i<boardSize;i++) {
		for(j=0;j<boardSize;j++) {
			board_set(nowa, i, j, board_get(b, i, j));
		}
	}		

	return nowa;
	
}

/* odczyt stanu gry z pliku */
int board_import(Board** b, const char* filename, int isCircleComputer,
                  int isCrossComputer) {

	const int BUFFER_SIZE = 100;

	FILE* f;
	int boardSize, penalty;
	symbol currentMove;
	float biddingPts;
	int i, j;
	char c;
	char* buffer;

	if(*b != NULL)
		board_cleanup(*b);

	f = fopen(filename, "r");

	if(fscanf(f, "%u %u %f\n", &boardSize, &penalty, &biddingPts) < 0) {

		fclose(f);
		*b = 0;
		return -1;
		
	}

	if(fscanf(f, "%c\n", &c) < 0) {

		fclose(f);
		*b = 0;
		return -1;
		
	}

	if(c == 'X')
		currentMove = CROSS;
	else if(c == 'O')
		currentMove = CIRCLE;
	else {
		fclose(f);
		*b = 0;
		return -1;
	}

	/* poprawność */
	if(boardSize >= 2 && boardSize <= 20 && penalty >= 1 &&
	   penalty <= 100 && biddingPts >= 0.0f) {}
	else {
		fclose(f);
		*b = 0;
		return -1;
	}
					  
	board_init(b, boardSize, penalty, 0.0f, biddingPts,
	                isCircleComputer, isCrossComputer);

	board_set_current_player_symbol(*b, currentMove);

	buffer = (char*)malloc(BUFFER_SIZE);

	for(i=0;i<boardSize;i++) {

		memset(buffer, 0, boardSize);

		fgets(buffer, BUFFER_SIZE, f);

		for(j=0;j<boardSize;j++) {

			if(buffer[j] == 'X')
				board_set(*b, i, j, CROSS);
			else if(buffer[j] == 'O')
				board_set(*b, i, j, CIRCLE);
			else if(buffer[j] == '_')
				board_set(*b, i, j, EMPTY);
			else {
				free(buffer);
				fclose(f);
				board_cleanup(*b);
				*b = 0;
				return -1;
			}
			
		}
		
	}

	free(buffer);
	
	fclose(f);

	/* przelicz wynik */
	(*b)->data->result = board_calculate_result(*b);

	return 0;

}

/* zapis stanu gry do pliku */
int board_export(Board* b, const char* filename) {

	float bidding_points;
	int i, j, boardSize;
	symbol s;
	FILE* f;

	/* otwieramy plik */
	f = fopen(filename, "w");

	boardSize = board_size(b);
	bidding_points = board_get_cross_bidding_points(b);

	/* ROZMIAR KARA_ZA_GRUPĘ PUNKTY_ZA_LICYTACJĘ */
	fprintf(f, "%u %u %.1f\n", board_size(b), board_get_penalty(b),
	        bidding_points);

	/* KTO_ZACZYNA */
	if(board_get_current_player_symbol(b) == CIRCLE)
		fprintf(f, "O\n");
	else
		fprintf(f, "X\n");

	for(i=0;i<boardSize;i++) {

		for(j=0;j<boardSize;j++) {

			s = board_get(b, i, j);

			if(s == EMPTY)
				fprintf(f, "_");
			else if(s == CROSS)
				fprintf(f, "X");
			else
				fprintf(f, "O");
			
		}

		fprintf(f, "\n");
		
	}

	fflush(f);
	fclose(f);

	return 0;
	
}

/* ustawienie bieżącego gracza */
void board_set_current_player_symbol(Board* b, symbol s) {

	b->data->currentMove = s;
	
}

symbol board_get_current_player_symbol(Board* b) {

	return b->data->currentMove;
	
}

/* czy ruch jest dozwolony? */
int board_is_move_allowed(Board* b, int x, int y) {

	return board_get (b, x, y) == EMPTY;
	
}

/* pobierz liczbę dozwolonych ruchów */
int board_get_allowed_moves_count(Board* b) {

	int i, j, boardSize;
	
	int howMany = 0;

	boardSize = board_size(b);

	for(i=0;i<boardSize;i++) {
		for(j=0;j<boardSize;j++) {
			if(board_is_move_allowed(b, i, j))
				howMany++;
		}
	}

	return howMany;
	
}

/* pobierz dozwolone ruchy */
Move* board_get_allowed_moves(Board* b) {

	int i, j, boardSize, index;
	int howManyAllowed;
	Move temp;
	Move* array;
	symbol s;

	boardSize = board_size(b);
	s = board_get_current_player_symbol(b);

	/* zliczamy poprawne ruchy */

	howManyAllowed = board_get_allowed_moves_count(b);

	array = (Move*)malloc(sizeof(Move)*howManyAllowed);

	/* Wyznaczamy poprawne ruchy */

	index = 0;

	for(i=0;i<boardSize;i++) {
		for(j=0;j<boardSize;j++) {
			if(board_is_move_allowed(b, i, j)) {
				temp.x = i;
				temp.y = j;
				temp.what = s;
				array[index++] = temp;
			}
		}
	}

	return array;
	
}

/* czy bieżący ruch należy do człowieka? */
int board_is_current_move_human(Board* b) {

	symbol s = board_get_current_player_symbol (b);

	if(s == CIRCLE)
		return !b->data->isCircleComputer;
	else
		return !b->data->isCrossComputer;
	
}

/* czy trwa gra komputer - komputer? */
int board_is_computer_vs_computer(Board* b) {

	return b->data->isCircleComputer && b->data->isCrossComputer;
	
}

/* czy koniec gry? */
int board_is_game_finished (Board* b) {

	int i, j;
	int boardSize;
	Move *temp, *temp2;
	int filledFields = 0;

	boardSize = board_size(b);

	/* zliczamy wypełnione pola */
	for(i=0;i<boardSize;i++) {
		for(j=0;j<boardSize;j++) {

			if(board_get(b, i, j) != EMPTY)
				filledFields++;
			
		}
	}

	/* wypełnienie planszy kończy grę */
	if(filledFields == boardSize*boardSize)
		return TRUE;

	/* 2 pasy kończą grę */
	if(stack_size(b->data->undoStack) >= 2) {

		temp = stack_pop(b->data->undoStack);
		temp2 = stack_pop(b->data->undoStack);

		stack_push(b->data->undoStack, temp2);
		stack_push(b->data->undoStack, temp);
		
		if(is_move_pass(temp) && is_move_pass(temp2))
		   return TRUE;
		
	}

	return FALSE;
	
}

int board_size(Board* b) {

	return b->data->n;
	
}

int board_get_penalty(Board* b) {

	return b->data->groupPenalty;
	
}

float board_get_cross_bidding_points(Board* b) {

	return b->data->komiCross;
	
}

/* zmiana kary za grupę */
void board_set_penalty(Board* b, int penalty) {

	b->data->groupPenalty = penalty;
	
}

/* zmiana punktów z licytacji dla krzyżyka */
void board_set_cross_bidding_points(Board* b, float f) {

	b->data->komiCross = f;
	
}

/* zmiana człowiek <-> komputer na kółku */
void board_set_circle_computer(Board* b, int logical) {

	b->data->isCircleComputer = logical;
	
}

/* zmiana człowiek <-> komputer na krzyżyku */
void board_set_cross_computer(Board* b, int logical) {

	b->data->isCrossComputer = logical;
	
}

symbol board_get(Board* b, int x, int y) {

	return b->data->fields[board_size(b)*x+y];
	
}

void board_play(Board* b, int x, int y) {

	Move *m;

	/* Nie wolno nadpisywać pól */
	if(b->data->fields[board_size(b)*x+y] != EMPTY)
		return;

	board_set(b, x, y, b->data->currentMove);

	/* dodajemy ruch do kolejki undo */
	
	m = (Move*)malloc(sizeof(Move));

	m->x = x;
	m->y = y;
	m->what = b->data->currentMove;

	stack_push(b->data->undoStack, m);

	/* zmień ruch do wykonania */
	if(b->data->currentMove == CIRCLE)
		b->data->currentMove = CROSS;
	else
		b->data->currentMove = CIRCLE;

	/* zaktualizuj wynik na planszy */
	b->data->result = board_calculate_result (b);
	
}

void board_play_move(Board* b, Move m) {

	if(m.x < 0 || m.y < 0)
		board_pass(b);
	else
		board_play(b, m.x, m.y);
	
}

void board_pass(Board* b) {

	/* dodajemy ruch do kolejki undo */

	Move *m;

	m = (Move*)malloc(sizeof(Move));

	m->x = -1;
	m->y = -1;
	m->what = board_get_current_player_symbol (b);

	stack_push(b->data->undoStack, m);

	/* zmień ruch do wykonania */
	if(b->data->currentMove == CIRCLE)
		b->data->currentMove = CROSS;
	else
		b->data->currentMove = CIRCLE;
	
}

void board_undo(Board* b) {

	/* operacje na kolejce undo - zdjęcie ostatniego elementu */

	Move* m = stack_pop(b->data->undoStack);
	
	if(m == NULL)
		return;

	/* jeżeli są dobre, współrzędne, cofamy ruch,
	 * pas ma współrzędne ujemne */
	if(m->x >= 0 && m->y >= 0)
		board_set(b, m->x, m->y, EMPTY);

	b->data->currentMove = m->what;
	
	free(m);

	/* przelicz wynik */
	b->data->result = board_calculate_result (b);
	
}

/* Publiczna funkcja działa nieco sprytniej, pobiera
 przetrzymywany w strukturze wynik */
Result board_result(Board* b) {

	return b->data->result;

}
